package org.tinygroup.dbrouterjdbc3.sqlprocessor;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.tinygroup.commons.tools.CollectionUtil;
import org.tinygroup.dbrouter.StatementProcessor;
import org.tinygroup.dbrouter.context.ResultSetExecutor;
import org.tinygroup.dbrouter.context.StatementExecuteContext;
import org.tinygroup.dbrouter.factory.RouterManagerBeanFactory;
import org.tinygroup.dbrouter.util.ParamObjectBuilder;
import org.tinygroup.dbrouterjdbc3.jdbc.TinyResultSetMultiple;
import org.tinygroup.dbrouterjdbc3.sqlprocessor.pagedata.FetchPageDataProcess;
import org.tinygroup.dbrouterjdbc3.sqlprocessor.pagedata.LimitPageDataProcess;
import org.tinygroup.dbrouterjdbc3.sqlprocessor.pagedata.PageData;
import org.tinygroup.dbrouterjdbc3.sqlprocessor.pagedata.PageDataProcess;
import org.tinygroup.jsqlparser.extend.ParameterFinder;
import org.tinygroup.jsqlparser.extend.ParameterFinder.ParameterEntry;
import org.tinygroup.jsqlparser.statement.Statement;
import org.tinygroup.jsqlparser.statement.select.PlainSelect;
import org.tinygroup.jsqlparser.statement.select.Select;
import org.tinygroup.jsqlparser.statement.select.SelectBody;

/**
 * 处理limitsql
 * 
 * @author renhui
 * 
 */
public class LimitSqlProcessor implements StatementProcessor {

	private List<PageDataProcess> processes = new ArrayList<PageDataProcess>();

	private ThreadLocal<PageData> threadLocal = new ThreadLocal<PageData>();

	public LimitSqlProcessor() {
		processes.add(new LimitPageDataProcess());
		processes.add(new FetchPageDataProcess());
	}

	public void addPageDataProcess(PageDataProcess process) {
		processes.add(process);
	}

	public void removePageDataProcess(PageDataProcess process) {
		processes.remove(process);
	}

	public boolean isMatch(String sql, Object[] values) {
		Statement statement = RouterManagerBeanFactory.getManager()
				.getSqlStatement(sql);
		if (statement instanceof Select) {
			Select select = (Select) statement;
			PageDataProcess dataProcess = getMatchProcess(select, processes);
			if (dataProcess != null) {
				ParameterFinder finder = new ParameterFinder();
				ParameterEntry entry = finder.getParameterEntry(select);
				PageData pageData = dataProcess.buildPageData(values, entry);
				threadLocal.set(pageData);
				return true;
			}
		}
		return false;
	}

	private PageDataProcess getMatchProcess(Select select,
			List<PageDataProcess> processes) {
		for (PageDataProcess pageDataProcess : processes) {
			if (pageDataProcess.isMatch(select)) {
				return pageDataProcess;
			}
		}
		return null;
	}

	public String getSql(String sql, StatementExecuteContext context) {
		Statement statement = RouterManagerBeanFactory.getManager()
				.getSqlStatement(sql);
		if (statement instanceof Select) {
			Select select = (Select) statement;
			SelectBody body = select.getSelectBody();
			if (body instanceof PlainSelect) {
				PlainSelect plainSelect = (PlainSelect) body;
				plainSelect.setLimit(null);
				plainSelect.setFetch(null);
				plainSelect.setOffset(null);
				removeParameter(context);
				return plainSelect.toString();
			}
		}
		return sql;
	}

	private void removeParameter(StatementExecuteContext context) {
		ParamObjectBuilder builder = context.getBuilder();
		if (builder != null) {
			try {
				PageData pageData = threadLocal.get();
				builder.removeParameterObject(pageData.getLimitIndex(),
						pageData.getLimitValue());
				builder.removeParameterObject(pageData.getStartIndex(),
						pageData.getStartValue());
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

	public ResultSet combineResult(String sql, StatementExecuteContext context)
			throws SQLException {
		List<ResultSetExecutor> resultSetExecutors = context
				.getResultSetExecutors();
		if (CollectionUtil.isEmpty(resultSetExecutors)) {
			return null;
		}
		PageData pageData = threadLocal.get();
		ResultSet resultSet = new TinyResultSetMultiple(pageData
				.getStartValue().intValue(), pageData.getLimitValue()
				.intValue(), context);
		return resultSet;
	}

}
